import sys
from argparse import ArgumentParser, RawTextHelpFormatter

from PySide6.QtCore import (QByteArray, QFile, QFileInfo, QSaveFile, QSettings,
                            QTextStream, Qt, Slot)
from PySide6.QtGui import QAction, QIcon, QKeySequence
from PySide6.QtWidgets import (QApplication, QFileDialog, QMainWindow,
                               QMessageBox, QTextEdit)

import application_rc  # noqa: F401

'''
https://doc.qt.io/qtforpython-6/examples/example_widgets_mainwindows_application.html#example-widgets-mainwindows-application
This application is a simple text editor built using PySide6, demonstrating how to create a modern GUI application
with a menu bar, toolbars, and a status bar. It supports basic file operations such as creating, opening, saving,
and editing text files. It demonstrates the analogous Qt example Application Example.
'''


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()

        self._test_tool_bar = None
        self._edit_tool_bar = None
        self._file_tool_bar = None
        self._help_menu = None
        self._edit_menu = None
        self._file_menu = None
        self._about_qt_act = None
        self._about_act = None
        self._paste_act = None
        self._copy_act = None
        self._cut_act = None
        self._exit_act = None
        self._save_as_act = None
        self._save_act = None
        self._open_act = None
        self._new_act = None
        self._cur_file = ''

        self._text_edit = QTextEdit()
        self.setCentralWidget(self._text_edit)

        self.create_actions()
        self.create_menus()
        self.create_tool_bars()
        self.create_status_bar()

        self.read_settings()

        self._text_edit.document().contentsChanged.connect(self.document_was_modified)

        self.set_current_file('')
        self.setUnifiedTitleAndToolBarOnMac(True)

    '''
    窗口关闭事件，点击[X]或者 执行 close 方法
    
    This event handler is called with the given event when Qt receives a window close request for a top-level widget from the window system.
    By default, the event is accepted and the widget is closed. You can reimplement this function to change the way the widget responds to window close requests. For example, you can prevent the window from closing by calling ignore() on all events.
    Main window applications typically use reimplementations of this function to check whether the user’s work has been saved and ask for permission before closing.
    '''

    def closeEvent(self, event):
        if self.maybe_save():
            self.write_settings()
            event.accept()  # 接收关闭窗口请求（By default, the event is accepted and the widget is closed）
        else:
            event.ignore()  # prevent the window from closing by calling ignore() on all events.

    @Slot()
    def new_file(self):
        if self.maybe_save():
            self._text_edit.clear()
            self.set_current_file('')

    @Slot()
    def open(self):
        if self.maybe_save():
            fileName, selectedFilter = QFileDialog.getOpenFileName(self)
            if fileName:
                self.load_file(fileName)

    @Slot()
    def save(self):
        if self._cur_file:  # 当前文件是 open 已经存在的，而不是新建的
            return self.save_file(self._cur_file)
        return self.save_as()  # 当前文件是 new 新建的，而不是 open 已经存在的

    @Slot()
    def save_as(self):
        fileName, selectedFilter = QFileDialog.getSaveFileName(self)
        if fileName:
            return self.save_file(fileName)  # 保存文件 成功 或者 失败
        return False  # 取消保存

    @Slot()
    def about(self):
        QMessageBox.about(self, "About Application",
                          "The <b>Application</b> example demonstrates how to write "
                          "modern GUI applications using Qt, with a menu bar, "
                          "toolbars, and a status bar.")

    '''
    A modified window is a window whose content has changed but has not been saved to disk. This flag will have different effects varied by the platform. On macOS the close button will have a modified look; on other platforms, the window title will have an ‘*’ (asterisk).
    The window title must contain a “[*]” placeholder, which indicates where the ‘*’ should appear. Normally, it should appear right after the file name (e.g., “document1.txt[*] - Text Editor”). If the window isn’t modified, the placeholder is simply removed.
    即：
    setWindowTitle(f"{shown_name}[*] - Application")添加 [*] 占位符，setWindowModified 控制是否显示 *
    '''

    @Slot()
    def document_was_modified(self):
        self.setWindowModified(self._text_edit.document().isModified())  # 标题栏添加显示*

    def create_actions(self):
        icon = QIcon.fromTheme(QIcon.ThemeIcon.DocumentNew, QIcon(':/images/new.png'))
        # icon = QIcon.fromTheme(QIcon.ThemeIcon.DocumentNew)
        self._new_act = QAction(icon, "&New", self)
        self._new_act.setShortcut(QKeySequence.StandardKey.New)
        self._new_act.setStatusTip("Create a new file")
        self._new_act.triggered.connect(self.new_file)

        icon = QIcon.fromTheme(QIcon.ThemeIcon.DocumentOpen, QIcon(':/images/open.png'))
        self._open_act = QAction(icon, "&Open", self)
        self._open_act.setShortcut(QKeySequence.StandardKey.Open)
        self._open_act.setStatusTip("Open an existing file")
        self._open_act.triggered.connect(self.open)

        icon = QIcon.fromTheme(QIcon.ThemeIcon.DocumentSave, QIcon(':/images/save.png'))
        self._save_act = QAction(icon, "&Save", self)
        self._save_act.setShortcut(QKeySequence.StandardKey.Save)
        self._save_act.setStatusTip("Save the document to disk")
        self._save_act.triggered.connect(self.save)

        self._save_as_act = QAction("Save &As...", self)
        self._save_as_act.setShortcut(QKeySequence.StandardKey.SaveAs)
        self._save_as_act.setStatusTip("Save the document under a new name")
        self._save_as_act.triggered.connect(self.save_as)

        icon = QIcon.fromTheme(QIcon.ThemeIcon.ApplicationExit)
        # self._exit_act = QAction(icon, "E&xit", self, shortcut="Ctrl+Q", statusTip="Exit the application", triggered=self.close)
        self._exit_act = QAction(icon, "E&xit", self)
        self._exit_act.setShortcut(QKeySequence.StandardKey.Quit)
        self._exit_act.setStatusTip("Exit the application")
        self._exit_act.triggered.connect(self.close)

        icon = QIcon.fromTheme(QIcon.ThemeIcon.EditCut, QIcon(':/images/cut.png'))
        self._cut_act = QAction(icon, "Cu&t", self)
        self._cut_act.setShortcut(QKeySequence.StandardKey.Cut)
        self._cut_act.setStatusTip("Cut the current selection's contents to the clipboard")
        self._cut_act.triggered.connect(self._text_edit.cut)

        icon = QIcon.fromTheme(QIcon.ThemeIcon.EditCopy, QIcon(':/images/copy.png'))
        self._copy_act = QAction(icon, "&Copy", self)
        self._copy_act.setShortcut(QKeySequence.StandardKey.Copy)
        self._copy_act.setStatusTip("Copy the current selection's contents to the clipboard")
        self._copy_act.triggered.connect(self._text_edit.copy)

        icon = QIcon.fromTheme(QIcon.ThemeIcon.EditPaste, QIcon(':/images/paste.png'))
        self._paste_act = QAction(icon, "&Paste", self)
        self._paste_act.setShortcut(QKeySequence.StandardKey.Paste)
        self._paste_act.setStatusTip("Paste the clipboard's contents into the current selection")
        self._paste_act.triggered.connect(self._text_edit.paste)

        icon = QIcon.fromTheme(QIcon.ThemeIcon.HelpAbout)
        self._about_act = QAction(icon, "&About", self)
        self._about_act.setStatusTip("Show the application's About box")
        self._about_act.triggered.connect(self.about)

        self._about_qt_act = QAction("About &Qt", self)
        self._about_qt_act.setStatusTip("Show the Qt library's About box")
        self._about_qt_act.triggered.connect(QApplication.aboutQt)

        self._cut_act.setEnabled(False)
        self._copy_act.setEnabled(False)
        '''
        copyAvailable(b)
        this signal is emitted when text is selected or de-selected in the text edit.
        When text is selected this signal will be emitted with yes set to true. If no text has been selected or if the selected text is de-selected this signal is emitted with yes set to false.
        If yes is true then copy() can be used to copy the selection to the clipboard. If yes is false then copy() does nothing.
        '''
        self._text_edit.copyAvailable.connect(self._cut_act.setEnabled)  # copyAvailable -> setEnabled，传递参数给 setEnabled
        self._text_edit.copyAvailable.connect(self._copy_act.setEnabled)

    def create_menus(self):
        self._file_menu = self.menuBar().addMenu("&File")  # Windows: 通常按 Alt 键时会显示下划线
        self._file_menu.addAction(self._new_act)
        self._file_menu.addAction(self._open_act)
        self._file_menu.addAction(self._save_act)
        self._file_menu.addAction(self._save_as_act)
        self._file_menu.addSeparator()
        self._file_menu.addAction(self._exit_act)

        self._edit_menu = self.menuBar().addMenu("&Edit")
        self._edit_menu.addAction(self._cut_act)
        self._edit_menu.addAction(self._copy_act)
        self._edit_menu.addAction(self._paste_act)

        self.menuBar().addSeparator()

        self._help_menu = self.menuBar().addMenu("&Help")
        self._help_menu.addAction(self._about_act)
        self._help_menu.addAction(self._about_qt_act)

    def create_tool_bars(self):
        self._file_tool_bar = self.addToolBar("File")  # 独立分组
        self._file_tool_bar.addAction(self._new_act)
        self._file_tool_bar.addAction(self._open_act)
        self._file_tool_bar.addAction(self._save_act)

        self._edit_tool_bar = self.addToolBar("Edit")  # 独立分组
        self._edit_tool_bar.addAction(self._cut_act)
        self._edit_tool_bar.addAction(self._copy_act)
        self._edit_tool_bar.addAction(self._paste_act)

    def create_status_bar(self):
        self.statusBar().showMessage("Ready")

    def read_settings(self):
        settings = QSettings('QtProject', 'Application Example')
        geometry = settings.value('geometry', QByteArray())
        if geometry.size():
            self.restoreGeometry(geometry)

    def write_settings(self):
        settings = QSettings('QtProject', 'Application Example')
        settings.setValue('geometry', self.saveGeometry())

    '''
    当前文本已保存
    '''

    def maybe_save(self):
        if self._text_edit.document().isModified():
            # 有文本变更，提示保存
            ret = QMessageBox.warning(self, "Application",
                                      "The document has been modified.\nDo you want to save your changes?",
                                      QMessageBox.StandardButton.Save | QMessageBox.StandardButton.Discard | QMessageBox.StandardButton.Cancel)
            if ret == QMessageBox.StandardButton.Save:
                return self.save()  # 选择保存按钮
            elif ret == QMessageBox.StandardButton.Cancel:
                return False  # 选择取消按钮
        return True  # 无文本变更，不需要保存 或者 选择放弃按钮

    '''
    QFile.OpenModeFlag.Text
    The Text flag passed to open() tells Qt to convert Windows-style line terminators (”\r\n”) into C++-style terminators (”\n”). By default, QFile assumes binary, i.e. it doesn’t perform any conversion on the bytes stored in the file.
    '''

    def load_file(self, fileName):
        file = QFile(fileName)
        if not file.open(QFile.OpenModeFlag.ReadOnly | QFile.OpenModeFlag.Text):
            reason = file.errorString()
            QMessageBox.warning(self, "Application", f"Cannot read file {fileName}:\n{reason}.")
            return

        inf = QTextStream(file)
        with QApplication.setOverrideCursor(Qt.CursorShape.WaitCursor):
            self._text_edit.setPlainText(inf.readAll())

        self.set_current_file(fileName)
        self.statusBar().showMessage("File loaded", 2000)

    def save_file(self, fileName):
        error = None
        with QApplication.setOverrideCursor(Qt.CursorShape.WaitCursor):
            file = QSaveFile(fileName)
            if file.open(QFile.OpenModeFlag.WriteOnly | QFile.OpenModeFlag.Text):
                outf = QTextStream(file)
                outf << self._text_edit.toPlainText()
                if not file.commit():
                    reason = file.errorString()
                    error = f"Cannot write file {fileName}:\n{reason}."
            else:
                reason = file.errorString()
                error = f"Cannot open file {fileName}:\n{reason}."

        if error:
            QMessageBox.warning(self, "Application", error)
            return False

        self.set_current_file(fileName)
        self.statusBar().showMessage("File saved", 2000)
        return True

    def set_current_file(self, fileName):
        self._cur_file = fileName
        self._text_edit.document().setModified(False)
        self.setWindowModified(False)

        if self._cur_file:
            shown_name = self.stripped_name(self._cur_file)
        else:
            # 初次打开程序和new文件时
            shown_name = 'untitled.txt'

        '''
        窗口标题中的[*]是Qt的一个特殊占位符，它的显示与否取决于窗口的修改状态（通过 setWindowModified() 控制）
        '''
        self.setWindowTitle(f"{shown_name}[*] - Application")

    def stripped_name(self, fullFileName):
        return QFileInfo(fullFileName).fileName()  # 去除文件路径后的文件全名


if __name__ == '__main__':
    argument_parser = ArgumentParser(description='Application Example', formatter_class=RawTextHelpFormatter)
    argument_parser.add_argument("file", help="File", nargs='?', type=str)
    options = argument_parser.parse_args()
    app = QApplication(sys.argv)
    main_win = MainWindow()
    if options.file:
        main_win.load_file(options.file)
    main_win.show()
    sys.exit(app.exec())
